#include <zenoui/widgets/TextInputWidget.hpp>
#include <zenoui/check_gl.hpp>
#include <zenoui/Game.hpp>
#include <zenox/glm.hpp>
#include <zenoui/FontData.hpp>
#include <zenox/unicode.hpp>

namespace zenoui {

void TextInputWidget::handle(MouseClickEvent *event) {
    if (event->button() == MouseLeftButton) [[likely]] {
        m_pressed = true;
        float pos = LabelWidget::label_query_index(event->pos());
        if (pos >= 0.0f) [[likely]] {
            m_cursor_pos = (int)std::round(pos);
            if (!(event->mods() & ModifierShift)) [[unlikely]] {
                m_selection_pos = m_cursor_pos;
            }
        }
        do_fit_cursor();
        zenox::log_trace("after mouse click cursor pos is now {}~{}", m_selection_pos, m_cursor_pos);
        event->accept(this);
    }
}

void TextInputWidget::handle(MouseMoveEvent *event) {
    if (event->button() == MouseLeftButton) [[likely]] {
        m_pressed = true;
        float pos = LabelWidget::label_query_index(event->pos());
        if (pos >= 0.0f) [[likely]] {
            m_cursor_pos = (int)std::round(pos);
        }
        do_fit_cursor();
        zenox::log_trace("after mouse move cursor pos is now {}~{}", m_selection_pos, m_cursor_pos);
        event->accept(this);
    }
}

void TextInputWidget::handle(MouseReleaseEvent *event) {
    m_pressed = false;
    event->accept(this);
}

void TextInputWidget::handle(KeyPressEvent *event) {
    do_fit_cursor();
    bool accepted = false, unselect = false;
    // TODO: implement Ctrl-C Ctrl-V?
    if (event->mods() & ModifierCtrl) [[unlikely]] {
        accepted = true;
        switch (event->key()) {
            case GLFW_KEY_C: {
                if (m_cursor_pos != m_selection_pos) [[likely]] {
                    glfwSetClipboardString(Game::instance().get_glfw_window(), zenox::u32tou8(do_copy_selection(false)).c_str());
                }
            } break;
            case GLFW_KEY_X: {
                if (!m_editable) [[unlikely]] {
                    accepted = false;
                    break;
                }
                if (m_cursor_pos != m_selection_pos) [[likely]] {
                    do_push_undo();
                    glfwSetClipboardString(Game::instance().get_glfw_window(), zenox::u32tou8(do_copy_selection(true)).c_str());
                }
            } break;
            case GLFW_KEY_V: {
                if (!m_editable) [[unlikely]] {
                    accepted = false;
                    break;
                }
                do_push_undo();
                do_insert_text(zenox::u8tou32(glfwGetClipboardString(Game::instance().get_glfw_window())), false);
            } break;
            case GLFW_KEY_A: {
                m_selection_pos = 0;
                m_cursor_pos = m_text.size();
            } break;
            case GLFW_KEY_Z: {
                if (!m_editable) [[unlikely]] {
                    accepted = false;
                    break;
                }
                do_pop_undo();
            } break;
            default: accepted = false;
        }
    }
    if (!accepted) [[likely]] {
        unselect = accepted = true;
        switch (event->key()) {
            case GLFW_KEY_ENTER: {
                if (!m_editable) [[unlikely]] {
                    unselect = accepted = false;
                    break;
                }
                do_push_undo();
                do_insert_char(U'\n');
            } break;
            case GLFW_KEY_BACKSPACE: {
                if (!m_editable) [[unlikely]] {
                    unselect = accepted = false;
                    break;
                }
                if (m_selection_pos == m_cursor_pos && m_cursor_pos > 0) [[likely]] {
                    m_selection_pos = m_cursor_pos - 1;
                }
                do_push_undo();
                do_delete_selection();
            } break;
            case GLFW_KEY_DELETE: {
                if (!m_editable) [[unlikely]] {
                    unselect = accepted = false;
                    break;
                }
                if (m_selection_pos == m_cursor_pos) [[likely]] {
                    m_selection_pos = m_cursor_pos + 1;
                }
                do_push_undo();
                do_delete_selection();
            } break;
            case GLFW_KEY_LEFT: { // TODO: support word jumping Ctrl+Left
                if (m_cursor_pos > 0) m_cursor_pos--;
            } break;
            case GLFW_KEY_RIGHT: {
                if (m_cursor_pos < m_text.size()) m_cursor_pos++;
            } break;
            case GLFW_KEY_HOME: {
                if (!m_text.empty() && m_cursor_pos > 0) [[likely]] {
                    size_t i = m_cursor_pos;
                    do i--;
                    while (i != 0 && m_text[i] != '\n');
                    if (i != 0) [[likely]] i++;
                    m_cursor_pos = i;
                }
            } break;
            case GLFW_KEY_END: {
                if (m_cursor_pos < m_text.size()) [[likely]] {
                    size_t i = m_cursor_pos;
                    while (m_text[i] != '\n') i++;
                    m_cursor_pos = i;
                }
            } break;
            case GLFW_KEY_UP: {
                if (!m_text.empty() && m_cursor_pos > 0) [[likely]] {
                    size_t i = m_cursor_pos;
                    do i--;
                    while (i != 0 && m_text[i] != '\n');
                    if (i != 0 || m_text[i] == '\n') [[likely]] m_cursor_pos = i;
                }
            } break;
            case GLFW_KEY_DOWN: {
                if (m_cursor_pos < m_text.size()) [[likely]] {
                    size_t i = m_cursor_pos;
                    while (m_text[i] != '\n') i++;
                    do i++;
                    while (i != m_text.size() && m_text[i] != '\n');
                    m_cursor_pos = i;
                }
            } break;
            default: unselect = accepted = false;
        }
    }
    if (accepted) {
        if (unselect && !(event->mods() & ModifierShift)) [[likely]] {
            m_selection_pos = m_cursor_pos;
        }
        zenox::log_trace("after key press cursor pos is now {}~{}", m_selection_pos, m_cursor_pos);
        event->accept(this);
    }
}

void TextInputWidget::handle(KeyReleaseEvent *event) {
    event->accept(this);
}

void TextInputWidget::do_fit_cursor() {
    if (m_cursor_pos > m_text.size()) [[unlikely]] {
        m_cursor_pos = m_text.size();
    }
    if (m_selection_pos > m_text.size()) [[unlikely]] {
        m_selection_pos = m_text.size();
    }
}

std::u32string TextInputWidget::do_copy_selection(bool is_cut) {
    do_fit_cursor();
    std::u32string ret(m_text.begin() + std::min(m_selection_pos, m_cursor_pos),
                       m_text.begin() + std::max(m_cursor_pos, m_selection_pos));
    if (is_cut) {
        do_delete_selection();
    }
    return ret;
}

void TextInputWidget::do_delete_selection() {
    do_fit_cursor();
    zenox::log_trace("deleting range {}~{}", m_selection_pos, m_cursor_pos);
    m_selection_pos = m_cursor_pos = m_text.erase(m_text.begin() + std::min(m_selection_pos, m_cursor_pos),
                                                  m_text.begin() + std::max(m_cursor_pos, m_selection_pos)) - m_text.begin();
    do_invalidate_text_cache();
    zenox::log_trace("deleted selection, updated cursor to {}", m_cursor_pos);
}

void TextInputWidget::do_push_undo() {
    m_undo_text.first = m_text;
    m_undo_text.second = m_cursor_pos;
}

void TextInputWidget::do_pop_undo() {
    TextProperty prop;
    std::swap(m_text, m_undo_text.first);
    std::swap(m_cursor_pos, m_undo_text.second);
    m_selection_pos = m_cursor_pos;
    do_invalidate_text_cache();
    do_fit_cursor();
}

void TextInputWidget::do_insert_text(std::u32string_view text, bool select_inserted) {
    do_fit_cursor();
    zenox::log_trace("text insert [{}] at {}~{}", zenox::u32tou8(text), m_selection_pos, m_cursor_pos);
    if (m_cursor_pos != m_selection_pos) [[unlikely]] {
        do_delete_selection();
    }
    m_text.insert(m_text.begin() + m_cursor_pos, text.begin(), text.end());
    do_invalidate_text_cache();
    m_cursor_pos += text.size();
    if (!select_inserted) [[likely]]
        m_selection_pos = m_cursor_pos;
    zenox::log_trace("cursor after insert is {}~{}", m_selection_pos, m_cursor_pos);
}

void TextInputWidget::do_insert_char(char32_t ch) {
    do_fit_cursor();
    zenox::log_trace("char insert U+{:04X} at {}~{}", (uint32_t)ch, m_selection_pos, m_cursor_pos);
    if (m_cursor_pos != m_selection_pos) [[unlikely]] {
        do_delete_selection();
    }
    m_text.insert(m_text.begin() + m_cursor_pos, ch);
    do_invalidate_text_cache();
    m_selection_pos = ++m_cursor_pos;
    zenox::log_trace("cursor after insert is {}", m_cursor_pos);
    /* auto str = UnicodeToASCII::encode(event->code()); */
    /* m_text.insert(m_text.begin() + std::min(m_cursor_pos, m_text.size()), str.begin(), str.end()); */
    /* m_cursor_pos += str.size(); */
}

void TextInputWidget::handle(CharInputEvent *event) {
    if (!m_editable) [[unlikely]] return;
    do_push_undo();
    do_insert_char(event->code());
}

void TextInputWidget::property(TextProperty *prop) {
    bool is_set = prop->has_value();
    if (is_set) {
        do_push_undo();
    }
    LabelWidget::property(prop);
    if (is_set) {
        m_selection_pos = m_cursor_pos = m_text.size();
    }
}

void TextInputWidget::paint() {
    m_button_rect.paint(rect(), m_background_color, true);
    LabelWidget::label_paint();
    auto lrect = LabelWidget::label_rect();
    auto rel_xy = LabelWidget::label_index_char_rel_xy(m_cursor_pos);
    auto curtopleft = lrect.m_topleft + rel_xy;
    glm::vec4 color = {0.3f, 0.4f, 0.5f, 0.75f};
    do_require_text_cache(16);
    auto fontsz = m_cached_font_data->font_size();
    CHECK_GL(glLineWidth(0.1f * fontsz.x));
    CHECK_GL(glColor4fv(glm::value_ptr(color)));
    glBegin(GL_LINES);
    glVertex2f(curtopleft.x, curtopleft.y);
    glVertex2f(curtopleft.x, curtopleft.y + fontsz.y);
    CHECK_GL(glEnd());
    if (m_selection_pos != m_cursor_pos) [[unlikely]] {
        auto rel_xy = LabelWidget::label_index_char_rel_xy(m_selection_pos);
        auto curtopleft = lrect.m_topleft + rel_xy;
        glm::vec4 color = {0.5f, 0.4f, 0.3f, 0.75f};
        CHECK_GL(glLineWidth(0.1f * fontsz.x));
        CHECK_GL(glColor4fv(glm::value_ptr(color)));
        glBegin(GL_LINES);
        glVertex2f(curtopleft.x, curtopleft.y);
        glVertex2f(curtopleft.x, curtopleft.y + fontsz.y);
        CHECK_GL(glEnd());
    }
}

TextInputWidget::TextInputWidget() {
    m_background_color = {0.8f, 0.8f, 0.8f, 1.0f};
    m_font_color = {0.25f, 0.18f, 0.1f, 1.0f};
    m_alignment = (TextAlignment)TextAlignTop | TextAlignLeft;
}

TextInputWidget::~TextInputWidget() = default;

}
